﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace Sublimation
{
    public class Unit : GameObject
    {
        protected Shield shield;
        public Shield Shield
        {
            get { return this.shield; }
        }

        protected float life;
        public float Life
        {
            get
            {
                return this.life;
            }
            set
            {
                this.life = Math.Max(value, 0);
            }
        }

        protected float meleeDamage;
        public float MeleeDamage
        {
            get { return this.meleeDamage; }
            set { this.meleeDamage = value; }
        }

        protected int meleeAttackDelay;
        public int MeleeAttackDelay
        {
            get { return this.meleeAttackDelay; }
            set { this.meleeAttackDelay = value; }
        }

        public Unit(WorldProperties worldProperties)
            : base(worldProperties)
        {
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            
            //TODO: remove this TEMP demo code
            //int damagePerSecond = 30;
            //Rectangle killZone = new Rectangle(
            //    worldProperties.Map.WorldBounds.X + worldProperties.Map.WorldBounds.Width / 2 - 150,
            //    worldProperties.Map.WorldBounds.Y + worldProperties.Map.WorldBounds.Height / 2 - 75,
            //    300,
            //    150);
            //if (killZone.Intersects(BoundingRectangle))
            //{
            //    ApplyDamage((int)Math.Round(damagePerSecond * gameTime.ElapsedGameTime.TotalSeconds, 0, MidpointRounding.AwayFromZero));
            //}

            // Does it survive this update?
            // die if there isnt enough health
            if (Life == 0)
            {
                Kill();
                return;
            }

            // update movement etc
            // TODO: have this move normally and collide, turn at speed etc

            // update melee timer
            meleeAttackTimerMillis = Math.Max(0, meleeAttackTimerMillis - gameTime.ElapsedGameTime.TotalMilliseconds);
        }

        public override void DoMovement(GameTime gameTime)
        {
            base.DoMovement(gameTime);

            this.Rotation = (float)Math.Atan2(Velocity.Y, Velocity.X);

            // check if the velocity is NaN (very small chance this can happen when a zero length vector is normalised)
//            if (!(float.IsNaN(Velocity.X) || float.IsNaN(Velocity.Y)))
//                this.Rotation = (float)Math.Acos(Vector2.Dot(Vector2.UnitX, Velocity) / Velocity.Length()) * Math.Sign(Vector2.Dot(Vector2.UnitY, Velocity));
//#if DEBUG
  //          else System.Diagnostics.Debug.WriteLine("Trying to rotate a NaN"); // this is because its normally a code error when velocity is NaN
//#endif
        }

        public override void ApplyDamage(float damage)
        {
            base.ApplyDamage(damage);

            // remove damage from shield if there is any
            if (Shield != null && Shield.ShieldEnergy > 0)
            {
                Shield.ApplyDamage(damage);
            }
            else
            {
                Life -= damage;
                if (Life == 0) Kill();
            }
        }

        public override void Kill()
        {
            base.Kill();

            // cleanup the components
            if (Shield != null) Shield.Kill();

            //TODO: an explosion and maybe dying sound (SPLAT)
        }

        public override void CheckCollisions(GameTime gameTime)
        {
            base.CheckCollisions(gameTime);

            //TODO: this
            // Physical objects

            // Towers
            foreach (Player player in worldProperties.Players)
            {
                foreach (Tower tower in player.Towers)
                {
                    if (CollidesWith(tower)) collideWithTower(tower, gameTime);
                }
            }
        }

        protected virtual void collideWithTower(Tower tower, GameTime gameTime)
        {
            //XXX: this is a bit of a performance hit
            // behaviour for hitting a tower
            Position = Position + Vector2.Normalize(Velocity) * (Vector2.Distance(GetRealCenter(), tower.GetRealCenter()) - CollisionRadius - tower.CollisionRadius); 
        }

        private double meleeAttackTimerMillis = 0;
        protected virtual void MeleeAttack(GameObject target)
        {
            if (meleeAttackTimerMillis == 0)
            {
                target.ApplyDamage(MeleeDamage);
                meleeAttackTimerMillis = MeleeAttackDelay;
            }
        }

        protected const float UnitAlignBehaviourWeight = 0.3f;
        protected const float UnitCohesionBehaviourWeight = 0.3f;
        protected const float SeparationDistance = 80.0f;
        protected const float UnitSeparationBehaviourWeight = 0.8f;
        protected const float UnitInertiaBehaviourWeight = 0.25f;
        protected const float UnitRandomBehaviourWeight = 0.05f;
        protected override void SetupBehaviours()
        {
            base.SetupBehaviours();

            this.behaviours.Add(new Behaviours.UnitAlignBehaviour(worldProperties, this, UnitAlignBehaviourWeight));
            this.behaviours.Add(new Behaviours.UnitCohesionBehaviour(worldProperties, this, UnitCohesionBehaviourWeight, SeparationDistance));
            this.behaviours.Add(new Behaviours.UnitInertiaBehaviour(worldProperties, this, UnitInertiaBehaviourWeight));
            this.behaviours.Add(new Behaviours.UnitRandomBehaviour(worldProperties, this, UnitRandomBehaviourWeight));
            this.behaviours.Add(new Behaviours.UnitSeparationBehaviour(worldProperties, this, UnitSeparationBehaviourWeight, SeparationDistance));
        }
    }
}